#!/bin/sh /etc/rc.common
#
# Copyright (C) 2017 openwrt-ssr
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#

START=90
STOP=15

SERVICE_DAEMONIZE=1
NAME=shadowsocksr
EXTRA_COMMANDS=cron
EXTRA_HELP="	cron  Run cron tasks"
CONFIG_FILE=/var/etc/${NAME}.json
CONFIG_UDP_FILE=/var/etc/${NAME}_u.json
CONFIG_SOCK5_FILE=/var/etc/${NAME}_s.json
CRON_FILE=/etc/crontabs/root
server_count=0
redir_tcp=0
redir_udp=0
tunnel_enable=0
local_enable=0
kcp_enable_flag=0
kcp_flag=0
dns_enable_flag=0
switch_enable=0
switch_server=$1

gfwlist_update_url="https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"
china_ip_update_url="http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest"

uci_get_by_name() {
	local ret=$(uci get $NAME.$1.$2 2>/dev/null)
	echo ${ret:=$3}
}

uci_get_by_type() {
	local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
	echo ${ret:=$3}
}

loger() {
	# 1.alert 2.crit 3.err 4.warn 5.notice 6.info 7.debug
	logger -st $NAME[$$] -p$1 $2
}

run_mode=$(uci_get_by_type global run_mode)

gen_config_file() {
	local host=$(uci_get_by_name $1 server)
	if echo $host | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
		hostip=${host}
	elif [ "$host" != "${host#*:[0-9a-fA-F]}" ]; then
		hostip=${host}
	else
		hostip=$(ping ${host} -s 1 -c 1 | grep PING | cut -d'(' -f 2 | cut -d')' -f1)
		if echo $hostip | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
			hostip=${hostip}
		else
			hostip=$(cat /etc/ssr_ip)
		fi
	fi
	[ $2 = "0" -a $kcp_flag = "1" ] && hostip="127.0.0.1"

	if [ $2 = "0" ]; then
		config_file=$CONFIG_FILE
	elif [ $2 = "1" ]; then
		config_file=$CONFIG_UDP_FILE
	else
		config_file=$CONFIG_SOCK5_FILE
	fi
	if [ $(uci_get_by_name $1 fast_open) = "1" ]; then
		fastopen="true"
	else
		fastopen="false"
	fi

	local port=$(uci_get_by_name $1 server_port)
	if [ $HAPROXY_MODE = "1" ]; then
		hostip="127.0.0.1"
		port="4433"
	fi

	local local_port=$(uci_get_by_name $1 local_port)
	iptables -A SS_WAN_DROP -p tcp --dport $local_port -j DROP

	cat <<-EOF >$config_file
		{
		    "server": "$hostip",
		    "server_port": $port,
		    "local_address": "0.0.0.0",
		    "local_port": $local_port,
		    "password": "$(uci_get_by_name $1 password)",
		    "timeout": $(uci_get_by_name $1 timeout 60),
		    "method": "$(uci_get_by_name $1 encrypt_method)",
		    "protocol": "$(uci_get_by_name $1 protocol)",
		    "protocol_param": "$(uci_get_by_name $1 protocol_param)",
		    "obfs": "$(uci_get_by_name $1 obfs)",
		    "obfs_param": "$(uci_get_by_name $1 obfs_param)",
		    "fast_open": $fastopen
		}
	EOF
}

get_arg_out() {
	case "$(uci_get_by_type access_control router_proxy 1)" in
		1) echo "-o" ;;
		2) echo "-O" ;;
	esac
}

start_rules() {
	local server=$(uci_get_by_name $GLOBAL_SERVER server)
	[ $HAPROXY_MODE = "1" ] && server="127.0.0.1"
	#resolve name
	if echo $server | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
		server=${server}
	elif [ "$server" != "${server#*:[0-9a-fA-F]}" ]; then
		server=${server}
	else
		server=$(ping ${server} -s 1 -c 1 | grep PING | cut -d'(' -f 2 | cut -d')' -f1)
		if echo $server | grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
			echo $server >/etc/ssr_ip
		else
			server=$(cat /etc/ssr_ip)
		fi
	fi

	kcp_server=$server

	local kcp_enable=$(uci_get_by_name $GLOBAL_SERVER kcp_enable)
	if [ "$kcp_enable" = "1" ]; then
		kcp_flag=1
	fi

	local local_port=$(uci_get_by_name $GLOBAL_SERVER local_port)
	local lan_ac_ips=$(uci_get_by_type access_control lan_ac_ips)
	local lan_ac_mode=$(uci_get_by_type access_control lan_ac_mode)
	local router_proxy=$(uci_get_by_type access_control router_proxy)
	if [ "$GLOBAL_SERVER" = "$UDP_RELAY_SERVER" -a $kcp_flag = 0 ]; then
		ARG_UDP="-u"
	elif [ -n "$UDP_RELAY_SERVER" ]; then
		ARG_UDP="-U"
		local udp_server=$(uci_get_by_name $UDP_RELAY_SERVER server)
		local udp_local_port=$(uci_get_by_name $UDP_RELAY_SERVER local_port)
	fi

	if [ -n "$lan_ac_ips" ]; then
		case "$lan_ac_mode" in
			w | W | b | B) local ac_ips="$lan_ac_mode$lan_ac_ips" ;;
		esac
	fi

	#deal	gfw firewall rule
	local gfwmode=""
	if [ "$run_mode" = "gfw" ]; then
		gfwmode="-g"
	fi

	/usr/bin/ssr-rules \
		-s "$server" \
		-l "$local_port" \
		-S "$udp_server" \
		-L "$udp_local_port" \
		-a "$ac_ips" \
		-i "$(uci_get_by_type access_control wan_bp_list)" \
		-b "$(uci_get_by_type access_control wan_bp_ips)" \
		-w "$(uci_get_by_type access_control wan_fw_ips)" \
		$(get_arg_out) $gfwmode $ARG_UDP
}

start_pdnsd() {
	local usr_dns="$1"
	local usr_port="$2"

	local tcp_dns_list="208.67.222.222, 208.67.220.220"
	[ -z "$usr_dns" ] && usr_dns="8.8.8.8"
	[ -z "$usr_port" ] && usr_port="53"
	mkdir -p /var/etc /var/pdnsd

	if ! test -f "/var/pdnsd/pdnsd.cache"; then
		dd if=/dev/zero of="/var/pdnsd/pdnsd.cache" bs=1 count=4 2>/dev/null
		chown -R nobody.nogroup /var/pdnsd
	fi

	cat >/var/etc/pdnsd.conf <<EOF
global {
	perm_cache=10240;
	cache_dir="/var/pdnsd";
	pid_file = /var/run/pdnsd.pid;
	run_as="nobody";
	server_ip = 127.0.0.1;
	server_port = 5353;
	status_ctl = on;
	query_method = tcp_only;
	min_ttl=1h;
	max_ttl=1w;
	timeout=10;
	neg_domain_pol=on;
	proc_limit=2;
	procq_limit=8;
}
server {
	label= "ssr-usrdns";
	ip = $usr_dns;
	port = $usr_port;
	timeout=6;
	uptest=none;
	interval=10m;
	purge_cache=off;
}
server {
	label= "ssr-pdnsd";
	ip = $tcp_dns_list;
	port = 5353;
	timeout=6;
	uptest=none;
	interval=10m;
	purge_cache=off;
}
EOF
	/usr/sbin/pdnsd -c /var/etc/pdnsd.conf -d
}

start_haproxy() {
	lan_ip=$(uci get network.lan.ipaddr)
	cat >/var/etc/haproxy.cfg <<EOF
global
	log 127.0.0.1 local2
	daemon
	pidfile /var/run/haproxy.pid
	maxconn 65536

defaults
	log global
	mode tcp
	maxconn 65536
	timeout connect  5000ms
	timeout client 30000ms
	timeout server 30000ms

listen stats
	bind $lan_ip:1111
	mode http
	maxconn 10
	stats refresh 30s
	stats uri /stats

frontend ss-in
	bind 127.0.0.1:4433
	default_backend ss-out

backend ss-out
	mode tcp
	balance roundrobin
EOF

	iptables -t nat -N HAPROXY

	for server in $(uci show -X shadowsocksr | grep servers | awk -F'[.=]' '{print $2}'); do
		local name=$(uci_get_by_name $server alias $server | sed 's/[^A-Za-z0-9_:.-]//g')
		local host=$(uci_get_by_name $server server)
		local port=$(uci_get_by_name $server server_port)
		local weight=$(uci_get_by_name $server weight 10)
		if [ -z "$host" ] || [ -z "$port" ]; then
			continue
		fi
		cat >>/var/etc/haproxy.cfg <<EOF
	server $name $host:$port weight $weight maxconn 4096 check inter 1500 rise 3 fall 3
EOF
		iptables -t nat -A HAPROXY -p tcp -d $host -j ACCEPT
	done

	iptables -t nat -I OUTPUT -j HAPROXY

	/usr/sbin/haproxy -q -D -f /var/etc/haproxy.cfg -p /var/run/haproxy.pid
}

dnsmasq_ssr() {
	sed -i '/--conf-dir=\/etc\/dnsmasq.ssr/d' /etc/init.d/dnsmasq
	if [ "$1" = "enable" ]; then
		sed -i '/args=""/a append args "--conf-dir=/etc/dnsmasq.ssr"' /etc/init.d/dnsmasq
	fi
	/etc/init.d/dnsmasq restart
}

start_redir() {
	case "$(uci_get_by_name $GLOBAL_SERVER auth_enable)" in
		1 | on | true | yes | enabled) ARG_OTA="-A" ;;
		*) ARG_OTA="" ;;
	esac

	#deal kcp
	local kcp_enable=$(uci_get_by_name $GLOBAL_SERVER kcp_enable)
	if [ "$kcp_enable" = "1" ]; then
		[ ! -f "/usr/bin/ssr-kcptun" ] && return 1

		local kcp_str=$(/usr/bin/ssr-kcptun -v | grep kcptun | wc -l)
		[ "0" = $kcp_str ] && return 1
		local kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port)
		local server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
		local password=$(uci_get_by_name $GLOBAL_SERVER kcp_password)
		local kcp_param=$(uci_get_by_name $GLOBAL_SERVER kcp_param)
		[ "$password" != "" ] && password="--key "${password}
		iptables -A SS_WAN_DROP -p tcp --dport $server_port -j DROP
		service_start /usr/bin/ssr-kcptun -r $kcp_server:$kcp_port -l :$server_port $password $kcp_param
		kcp_enable_flag=1
	fi

	gen_config_file $GLOBAL_SERVER 0

	redir_tcp=1
	local last_config_file=$CONFIG_FILE
	local pid_file="/var/run/ssr-retcp.pid"

	if [ "$ARG_UDP" = "-U" ]; then
		/usr/bin/ssr-redir \
			-c $CONFIG_FILE $ARG_OTA \
			-f /var/run/ssr-retcp.pid

		case "$(uci_get_by_name $UDP_RELAY_SERVER auth_enable)" in
			1 | on | true | yes | enabled) ARG_OTA="-A" ;;
			*) ARG_OTA="" ;;
		esac
		gen_config_file $UDP_RELAY_SERVER 1
		last_config_file=$CONFIG_UDP_FILE
		pid_file="/var/run/ssr-reudp.pid"
		redir_udp=1
	fi

	/usr/bin/ssr-redir \
		-c $last_config_file $ARG_OTA $ARG_UDP \
		-f $pid_file

	# deal with dns
	if [ "$run_mode" = "gfw" ] ;then
		if [ "$(uci_get_by_type global pdnsd_enable)" != "1" ]; then
			iptables -A SS_WAN_DROP -p tcp --dport 5353 -j DROP
			service_start /usr/bin/ssr-tunnel -c $CONFIG_FILE -b 0.0.0.0 -u -l 5353 -L $(uci_get_by_type global tunnel_forward 8.8.4.4:53) -f /var/run/ssr-dns.pid
			dns_enable_flag=1
		else
			local dnsstr="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)"
			local dnsserver=$(echo "$dnsstr" | awk -F ':' '{print $1}')
			local dnsport=$(echo "$dnsstr" | awk -F ':' '{print $2}')
			ipset add gfwlist $dnsserver 2>/dev/null
			start_pdnsd $dnsserver $dnsport
		fi
		gfw_list=$(uci_get_by_type global gfw_list)
		if [ -n "$gfw_list" ]; then
			cat <<-EOF > /etc/dnsmasq.ssr/gfw_luci.conf
$(for d in $gfw_list; do echo "server=/.$d/127.0.0.1#5353"; echo "ipset=/.$d/gfwlist"; done)
EOF
		else
        	rm -f /etc/dnsmasq.ssr/gfw_luci.conf
		fi
		dnsmasq_ssr "enable"
	fi

	if [ "$(uci_get_by_type global enable_switch)" = "1" ] && [ $HAPROXY_MODE = "0" ]; then
		if [ "$(uci_get_by_name $GLOBAL_SERVER switch_enable)" = "1" ]; then
			if [ -z "$switch_server" ]; then
				local switch_time=$(uci_get_by_type global switch_time)
				local switch_timeout=$(uci_get_by_type global switch_timeout)
				service_start /usr/bin/ssr-switch start $switch_time $switch_timeout
				switch_enable=1
			fi
		fi
	fi

	return $?
}

gen_service_file() {
	if [ $(uci_get_by_name $1 fast_open) = "1" ]; then
		fastopen="true"
	else
		fastopen="false"
	fi
	cat <<-EOF >$2
		{
		    "server": "$(uci_get_by_name $1 server)",
		    "server_port": $(uci_get_by_name $1 server_port),
		    "password": "$(uci_get_by_name $1 password)",
		    "timeout": $(uci_get_by_name $1 timeout 60),
		    "method": "$(uci_get_by_name $1 encrypt_method)",
		    "protocol": "$(uci_get_by_name $1 protocol)",
		    "obfs": "$(uci_get_by_name $1 obfs)",
		    "obfs_param": "$(uci_get_by_name $1 obfs_param)",
		    "fast_open": $fastopen
		}
	EOF
}

start_service() {
	[ $(uci_get_by_name $1 enable) = "0" ] && return 1
	let server_count=server_count+1
	if [ $server_count = 1 ]; then
		iptables -N SSR-SERVER-RULE &&
			iptables -t filter -I INPUT -j SSR-SERVER-RULE
	fi

	gen_service_file $1 /var/etc/${NAME}_${server_count}.json
	/usr/bin/ssr-server -c /var/etc/${NAME}_${server_count}.json -u -f /var/run/ssr-server${server_count}.pid
	iptables -t filter -A SSR-SERVER-RULE -p tcp --dport $(uci_get_by_name $1 server_port) -j ACCEPT
	iptables -t filter -A SSR-SERVER-RULE -p udp --dport $(uci_get_by_name $1 server_port) -j ACCEPT
	return 0
}

gen_serv_include() {
	FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null)
	[ -n "$FWI" ] || return 0
	if [ ! -f $FWI ]; then
		echo '#!/bin/sh' >$FWI
	fi
	extract_rules() {
		echo "*filter"
		iptables-save -t filter | grep SSR-SERVER-RULE | sed -e "s/^-A INPUT/-I INPUT/"
		echo 'COMMIT'
	}
	cat <<-EOF >>$FWI
		iptables-restore -n <<-EOT
		$(extract_rules)
		EOT
	EOF

}

start_server() {
	SERVER_ENABLE=$(uci_get_by_type server_global enable_server)
	[ "$SERVER_ENABLE" = 0 ] && return 0
	mkdir -p /var/run /var/etc

	config_load $NAME
	config_foreach start_service server_config
	gen_serv_include
	return 0
}

start_tunnel() {
	/usr/bin/ssr-tunnel \
		-c $CONFIG_FILE $ARG_OTA ${ARG_UDP:="-u"} \
		-l $(uci_get_by_type global tunnel_port 5300) \
		-L $(uci_get_by_type global tunnel_forward 8.8.4.4:53) \
		-f /var/run/ssr-tunnel.pid
	tunnel_enable=1
	return $?
}

start_local() {
	[ $(uci_get_by_type global enable_ssr_local 0) = 0 ] && return 1
	local local_server=$(uci_get_by_type global ssr_local_server)
	[ -z "$local_server" ] && return 1
	mkdir -p /var/run /var/etc
	gen_config_file $local_server 2
	/usr/bin/ssr-local -c $CONFIG_SOCK5_FILE -u \
		-l $(uci_get_by_type global ssr_local_port 1080) \
		-f /var/run/ssr-local.pid
	local_enable=1
}

rules() {
	if [ $(uci_get_by_type global enable) = 0 ] || [ -z "$GLOBAL_SERVER" ]; then
		return 1
	fi
	mkdir -p /var/run /var/etc
	UDP_RELAY_SERVER=$(uci_get_by_type global udp_relay_server)
	[ "$UDP_RELAY_SERVER" = "same" ] && UDP_RELAY_SERVER=$GLOBAL_SERVER
	start_rules
}

cron() {
	update_conf() {
		new_count=$(cat $1 | wc -l)
		if [ -f "$2" ]; then
			old_count=$(cat $2 | wc -l)
		else
			old_count="0"
		fi
		if [ "$new_count" -gt "1000" ] && [ "$old_count" -ne "$new_count" ]; then
			cp -f $1 $2 && rm -f $1
			return $?
		else
			touch $2 && rm -f $1
			return 1
		fi
	}

	case "$1" in
		gfwlist)
			loger 6 "Updating gfwlist from $gfwlist_update_url..."
			gfwlist_conf="/etc/dnsmasq.ssr/gfw_list.conf"
			if curl --connect-timeout 3 -sLo /tmp/gfw.b64 $gfwlist_update_url && /usr/bin/ssr-gfw; then
				update_conf /tmp/gfwnew.txt $gfwlist_conf && /etc/init.d/dnsmasq restart
				loger 6 "gfwlist update success! [$gfwlist_conf: $(cat $gfwlist_conf | wc -l) lines]"
			else
				retcode=$?
				loger 3 "gfwlist update failed!"
				return $retcode
			fi
		;;
		china_ip)
			loger 6 "Updating china ip from $china_ip_update_url..."
			china_ip_conf="/etc/china_ssr.txt"
			if curl --connect-timeout 3 -sLo- $china_ip_update_url 2>/dev/null \
				| awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' \
				> /tmp/china_ssr.txt; then
				update_conf /tmp/china_ssr.txt $china_ip_conf
				loger 6 "china ip update success! [$china_ip_conf: $(cat $china_ip_conf | wc -l) lines]"
			else
				retcode=$?
				loger 3 "china ip update failed!"
				return $retcode
			fi
		;;
		*)
			echo "Usage: $0 cron gfwlist|china_ip" && exit 1
		;;
	esac
}

add_cron() {
	local update_time=$(uci_get_by_type global subscribe_update_time 2)
	sed -i '/shadowsocksr/d' $CRON_FILE
	sed -i '/ssr-subscribe/d' $CRON_FILE
	cat <<-EOF >> $CRON_FILE
5 0 * * * /etc/init.d/shadowsocksr cron china_ip
0 0 * * * /etc/init.d/shadowsocksr cron gfwlist
$([ $(uci_get_by_type global subscribe_enable 0) = 1 ] && echo "0 $update_time * * * /usr/bin/ssr-subscribe")
EOF
	/etc/init.d/cron restart
}

del_cron() {
	sed -i '/shadowsocksr/d' $CRON_FILE
	sed -i '/ssr-subscribe/d' $CRON_FILE
	/etc/init.d/cron restart
}

start() {
	if [ -z "$switch_server" ]; then
		GLOBAL_SERVER=$(uci_get_by_type global global_server)
	else
		GLOBAL_SERVER=$switch_server
		switch_enable=1
	fi

	HAPROXY_MODE="0"
	if [ "$GLOBAL_SERVER" = "__haproxy__" ]; then
		GLOBAL_SERVER=$(uci show -X shadowsocksr | grep servers | awk -F'[.=]' '{print $2}' | head -n 1)
		HAPROXY_MODE="1"
	fi

	iptables -N SS_WAN_DROP
	iptables -I INPUT -m ifalias --inif wan --wildcard -j SS_WAN_DROP

	if rules; then
		[ $HAPROXY_MODE = "1" ] && start_haproxy

		add_cron
		start_redir

		if [ "$run_mode" = "router" ] ;then
			case "$(uci_get_by_type global tunnel_enable)" in
				1 | on | true | yes | enabled)
					start_tunnel
					;;
			esac
		fi
	fi

	start_server
	start_local

	if [ $(uci_get_by_type global monitor_enable) = 1 ]; then
		let total_count=server_count+redir_tcp+redir_udp+tunnel_enable+kcp_enable_flag+local_enable+dns_enable_flag+switch_enable
		if [ $total_count -gt 0 ]; then
			#param:server(count) redir_tcp(0:no,1:yes)  redir_udp tunnel kcp local gfw
			service_start /usr/bin/ssr-monitor $server_count $redir_tcp $redir_udp $tunnel_enable $kcp_enable_flag $local_enable $dns_enable_flag $switch_enable
		fi
	fi
}

stop() {
	del_cron

	/usr/bin/ssr-rules -f
	srulecount=$(iptables -L | grep SSR-SERVER-RULE | wc -l)
	if [ $srulecount -gt 0 ]; then
		iptables -F SSR-SERVER-RULE
		iptables -t filter -D INPUT -j SSR-SERVER-RULE
		iptables -X SSR-SERVER-RULE 2>/dev/null
	fi

	iptables -t nat -F HAPROXY 2>/dev/null
	iptables -t nat -D OUTPUT -j HAPROXY 2>/dev/null
	iptables -t nat -X HAPROXY 2>/dev/null

	iptables -F SS_WAN_DROP 2>/dev/null
	iptables -D INPUT -m ifalias --inif wan --wildcard -j SS_WAN_DROP 2>/dev/null
	iptables -X SS_WAN_DROP 2>/dev/null

	killall -q -9 ssr-monitor
	if [ -z "$switch_server" ]; then
		killall -q -9 ssr-switch
	fi
	killall -q -9 ssr-redir
	killall -q -9 ssr-tunnel
	killall -q -9 ssr-server
	killall -q -9 ssr-kcptun
	killall -q -9 ssr-local
	killall -q -9 pdnsd
	killall -q -9 haproxy

	dnsmasq_ssr "disable"
}
